{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import torch \n",
    "import torch.nn as nn\n",
    "import torch.nn.init as init\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "import torchvision\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torchvision\n",
    "import torchvision.transforms as transforms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cpu\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "    cuda = torch.cuda.is_available()\n",
    "    cuda = \"cpu\"\n",
    "    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "    print(cuda)\n",
    "    print(device)\n",
    "    \n",
    "    random_seed = 1\n",
    "    torch.manual_seed(random_seed)\n",
    "    batch_size_train = 100\n",
    "    batch_size_test = 1000\n",
    "    image_size = [28, 28]\n",
    "    center = 28\n",
    "    trainloader = DataLoader(\n",
    "            torchvision.datasets.MNIST('./data/', train=True, download=True,\n",
    "                transform=torchvision.transforms.Compose([\n",
    "                    torchvision.transforms.CenterCrop(center),\n",
    "                    torchvision.transforms.Resize(image_size),\n",
    "                    torchvision.transforms.ToTensor(),\n",
    "#                    torchvision.transforms.Normalize(\n",
    "#                        (0.1307,), (0.3081) )\n",
    "                    ])),\n",
    "            batch_size=batch_size_train, shuffle=True)\n",
    "    testloader = DataLoader(\n",
    "            torchvision.datasets.MNIST('./data/', train=False, download=True,\n",
    "                transform=torchvision.transforms.Compose([\n",
    "                    torchvision.transforms.CenterCrop(center),\n",
    "                    torchvision.transforms.Resize(image_size),\n",
    "                    torchvision.transforms.ToTensor(),\n",
    "#                    torchvision.transforms.Normalize(\n",
    "#                        (0.1307,), (0.3081,) )\n",
    "                    ])),\n",
    "                batch_size=batch_size_test, shuffle=True\n",
    "            )\n",
    "    examples = enumerate(testloader)\n",
    "    batch_idx, (example_data, example_targets) = next(examples)\n",
    "    #for i in range(1):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "classes = ('0', '1', '2', '3',\n",
    "           '4', '5', '6', '7', '8', '9')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    3     6     4     1\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# functions to show an image\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "%matplotlib inline\n",
    "def imshow(img):\n",
    "    img = img / 2 + 0.5 # unnormalize\n",
    "    npimg = img.numpy()\n",
    "    plt.imshow(np.transpose(npimg, (1,2,0)))\n",
    "\n",
    "# show some random training images\n",
    "dataiter = iter(trainloader)\n",
    "images, labels = dataiter.next()\n",
    "\n",
    "# print images\n",
    "imshow(torchvision.utils.make_grid(images))\n",
    "# print labels\n",
    "print(' '.join('%5s'%classes[labels[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class fs_relu(nn.Module):\n",
    "    def __init__(self, relu_K, alpha, index):\n",
    "        super(fs_relu, self).__init__()\n",
    "        self.index = index\n",
    "        self.K = relu_K\n",
    "        self.h = alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)\n",
    "        self.d = alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)\n",
    "        self.T = alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)\n",
    "\n",
    "    def forward(self, x, time = -1):\n",
    "        if (time == -1):\n",
    "            self.Vmem = torch.zeros(x.size())\n",
    "            out = torch.zeros(x.size())\n",
    "            self.spike = torch.zeros(x.size())\n",
    "        elif ( (time >= (self.index-1)*self.K) and (time < self.index * self.K) ):\n",
    "            self.Vmem = self.Vmem + x\n",
    "            out = torch.zeros(x.size())\n",
    "            self.spike = torch.zeros(x.size())\n",
    "        elif ( (time >= self.index*self.K) and time < ((self.index+1)*self.K)):\n",
    "            t = time - (self.index*self.K)\n",
    "            self.spike = self.Vmem >= self.T[t];\n",
    "            self.Vmem = self.Vmem - self.spike * self.h[t]\n",
    "            out = self.spike * self.d[t]\n",
    "        else:\n",
    "            self.spike = torch.zeros(x.size())\n",
    "            out = torch.zeros(x.size())\n",
    "        return out\n",
    "\n",
    "class fs_pool(nn.Module):\n",
    "    def __init__(self, K, kernel, stride):\n",
    "        super(fs_pool, self).__init__()\n",
    "        self.K = K\n",
    "        self.kernel = kernel\n",
    "        self.stride = stride\n",
    "        self.pool = nn.AvgPool2d(kernel, stride=stride)\n",
    "        \n",
    "    def forward(self, x, index, time=-1):\n",
    "        if (time==-1):\n",
    "            self.sum = torch.zeros(x.size())\n",
    "            self.ans = torch.zeros(x.size())\n",
    "        elif ( (time>=(index-1)*self.K) and (time<index*self.K) ):\n",
    "            self.sum = self.sum + x\n",
    "            self.ans = torch.zeros(x.size())\n",
    "            \n",
    "        if ( time == index*self.K-1):\n",
    "            self.ans = self.sum\n",
    "            print(time)\n",
    "        else :\n",
    "            self.ans = torch.zeros(x.size())\n",
    "        \n",
    "        out = self.pool(self.ans)\n",
    "\n",
    "        return out\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "class lenet55(nn.Module):\n",
    "    \n",
    "    def __init__(self):\n",
    "        super(lenet55, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 6, 5, bias=False)\n",
    "        self.pool1 = nn.AvgPool2d(2, stride=2)\n",
    "        self.conv2 = nn.Conv2d(6,16, 5, bias=False)\n",
    "        self.pool2 = nn.AvgPool2d(2, stride=2)\n",
    "        self.relu  = nn.ReLU()\n",
    "        self.fc1   = nn.Linear(16*4*4, 120, bias=False)\n",
    "        self.fc2   = nn.Linear(120, 84, bias=False)\n",
    "        self.fc3   = nn.Linear(84, 10, bias=False)\n",
    "        \n",
    "        self.fs_pool1 = fs_pool(15, 2, stride=2)\n",
    "        self.fs_pool2 = fs_pool(15, 2, stride=2)\n",
    "        self.fs_relu1 = fs_relu(15, 25, 1)\n",
    "        self.fs_relu2 = fs_relu(15, 25, 2)\n",
    "        self.fs_relu3 = fs_relu(15, 25, 3)\n",
    "        self.fs_relu4 = fs_relu(15, 25, 4)\n",
    "        self.fs_relu5 = fs_relu(15, 25, 5)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        x = self.pool1(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.conv2(x)\n",
    "        x = self.pool2(x)\n",
    "        x = self.relu(x)\n",
    "        x = x.view(-1, self.num_flat_features(x))\n",
    "        x = self.relu(self.fc1(x))\n",
    "        x = self.relu(self.fc2(x))\n",
    "        x = self.fc3(x)\n",
    "        return x\n",
    "    \n",
    "    def fs_forward(self, x, time):\n",
    "        x = self.conv1(x)\n",
    "        x = self.pool1(x)\n",
    "        x = self.fs_relu1(x, time)\n",
    "        x = self.conv2(x)\n",
    "        # x = self.pool2(x)\n",
    "        x = self.fs_pool2(x, 2, time)\n",
    "        x = self.fs_relu2(x, time)\n",
    "        x = x.view(-1, self.num_flat_features(x))\n",
    "        x = self.fs_relu3(self.fc1(x), time)\n",
    "        x = self.fs_relu4(self.fc2(x), time)\n",
    "        x = self.fs_relu5(self.fc3(x), time)\n",
    "        return x\n",
    "#         return self.fs_relu5.spike\n",
    "    \n",
    "    def num_flat_features(self, x):\n",
    "        size = x.size()[1:] # all dimensions except the batch dimension\n",
    "        num_features = 1\n",
    "        for s in size:\n",
    "            num_features *= s\n",
    "        return num_features\n",
    "        \n",
    "    def image_to_cfg(self, image, label, src, tgt):\n",
    "        cfg = {}\n",
    "        cfg[\"channel\"] = image.size()[0]\n",
    "        cfg[\"height\"] = image.size()[1]\n",
    "        cfg[\"width\"] = image.size()[2]\n",
    "        cfg[\"label\"] = int(label)\n",
    "        cfg[\"features\"] = image.view(-1).numpy().tolist()\n",
    "        cfg[\"recv_pipeline\"] = 2\n",
    "        cfg[\"send_pipeline\"] = 2\n",
    "        relu_K = 15\n",
    "        alpha = 25\n",
    "        cfg[\"fs_phase\"] = 1\n",
    "        cfg[\"fs_k\"] = relu_K\n",
    "        cfg[\"fs_h\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        cfg[\"fs_d\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        cfg[\"fs_t\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        # featrues\n",
    "        file = \"./lenet5_fs_cfg/features_map{}.json\".format(src)\n",
    "        fp = open(file,'w')\n",
    "        print(json.dumps(cfg,indent=1),file=fp)\n",
    "        fp.close()\n",
    "        # axon\n",
    "        syn = {}\n",
    "        syn[\"type\"] = \"features\"\n",
    "        axon = []\n",
    "        for c in range(cfg[\"channel\"]):\n",
    "            for h in range(cfg[\"height\"]):\n",
    "                for w in range(cfg[\"width\"]):\n",
    "                    axon.append([tgt, c, h, w])\n",
    "        syn[\"axons\"] = axon\n",
    "        syn[\"denrites\"] = []\n",
    "        file = \"./lenet5_fs_cfg/synapse{}.json\".format(src)\n",
    "        fp = open(file, 'w')\n",
    "        print(json.dumps(syn,indent=1), file=fp)\n",
    "        \n",
    "\n",
    "    def conv_to_conv_cfg(self, features, pool, conv, weight, src, tgt, flat=False):\n",
    "        cfg = {}\n",
    "        cfg[\"type\"] = \"conv\"\n",
    "        cfg[\"pool_h\"] = pool[0]\n",
    "        cfg[\"pool_w\"] = pool[1]\n",
    "        cfg[\"pool_s\"] = pool[2]\n",
    "        cfg[\"conv_c\"] = conv[0]\n",
    "        cfg[\"conv_h\"] = conv[1]\n",
    "        cfg[\"conv_w\"] = conv[2]\n",
    "        cfg[\"conv_s\"] = conv[3]\n",
    "        cfg[\"denrites\"] = weight.detach().numpy().tolist()\n",
    "        \n",
    "        axon = []\n",
    "        cfg[\"inChannel\"] = weight.size()[1]\n",
    "        cfg[\"channel\"] = features[0]\n",
    "        cfg[\"height\"] = features[1]\n",
    "        cfg[\"width\"] = features[2]\n",
    "        k = 0\n",
    "        for c in range(cfg[\"channel\"]):\n",
    "            for h in range(cfg[\"height\"]):\n",
    "                for w in range(cfg[\"width\"]):\n",
    "                    if (flat):           \n",
    "                        axon.append([tgt,k])\n",
    "                        k=k+1\n",
    "                    else:\n",
    "                        axon.append([tgt,c,h,w])\n",
    "        cfg[\"axons\"] = axon\n",
    "        \n",
    "        file = \"./lenet5_fs_cfg/synapse{}.json\".format(src)\n",
    "        fp = open(file, 'w')\n",
    "        print(json.dumps(cfg,indent=1), file=fp)\n",
    "        fp.close()\n",
    "        \n",
    "        neu = {}\n",
    "        neu[\"recv_pipeline\"] = 2\n",
    "        neu[\"send_pipeline\"] = 2\n",
    "        neu[\"type\"] = \"conv\"\n",
    "        neu[\"pool_h\"] = pool[0]\n",
    "        neu[\"pool_w\"] = pool[1]\n",
    "        neu[\"pool_s\"] = pool[2]\n",
    "        neu[\"conv_c\"] = conv[0]\n",
    "        neu[\"conv_h\"] = conv[1]\n",
    "        neu[\"conv_w\"] = conv[2]\n",
    "        neu[\"conv_s\"] = conv[3]     \n",
    "        neu[\"inChannel\"] = weight.size()[1]\n",
    "        neu[\"channel\"] = features[0]\n",
    "        neu[\"height\"] = features[1]\n",
    "        neu[\"width\"] = features[2]\n",
    "        relu_K = 15\n",
    "        alpha = 25\n",
    "        neu[\"fs_phase\"] = 1\n",
    "        neu[\"fs_k\"] = relu_K\n",
    "        neu[\"fs_h\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        neu[\"fs_d\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        neu[\"fs_t\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        file = \"./lenet5_fs_cfg/neuron{}.json\".format(src)\n",
    "        fp = open(file, 'w')\n",
    "        print(json.dumps(neu,indent=1), file=fp)\n",
    "        fp.close()\n",
    "        \n",
    "    def linear_to_linear_cfg(self, channel, weight, src, tgt, pool=[1,1,1]):\n",
    "        cfg = {}\n",
    "        cfg[\"type\"] = \"linear\"\n",
    "        cfg[\"denrites\"] = weight.detach().numpy().tolist()\n",
    "        cfg[\"inChannel\"] = weight.size()[1]\n",
    "        cfg[\"channel\"] = channel\n",
    "        \n",
    "        axon = []\n",
    "        for c in range(channel):\n",
    "            axon.append([tgt,c])\n",
    "        cfg[\"axons\"] = axon\n",
    "        \n",
    "        file = \"./lenet5_fs_cfg/synapse{}.json\".format(src)\n",
    "        fp = open(file, 'w')\n",
    "        print(json.dumps(cfg,indent=1), file=fp)\n",
    "        \n",
    "        neu = {}\n",
    "        neu[\"type\"] = \"linear\"\n",
    "        neu[\"recv_pipeline\"] = 2\n",
    "        neu[\"send_pipeline\"] = 2\n",
    "        neu[\"inChannel\"] = weight.size()[1]\n",
    "        neu[\"channel\"] = channel\n",
    "        neu[\"pool_h\"] = pool[0]\n",
    "        neu[\"pool_w\"] = pool[1]\n",
    "        neu[\"pool_s\"] = pool[2]\n",
    "        relu_K = 15\n",
    "        alpha = 25\n",
    "        neu[\"fs_phase\"] = 1\n",
    "        neu[\"fs_k\"] = relu_K\n",
    "        neu[\"fs_h\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        neu[\"fs_d\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        neu[\"fs_t\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        file = \"./lenet5_fs_cfg/neuron{}.json\".format(src)\n",
    "        fp = open(file, 'w')\n",
    "        print(json.dumps(neu,indent=1), file=fp)\n",
    "        fp.close()\n",
    "    \n",
    "    def output_cfg(self, channel, src):\n",
    "        neu = {}\n",
    "        neu[\"type\"] = \"output\"\n",
    "        neu[\"recv_pipeline\"] = 2\n",
    "        neu[\"send_pipeline\"] = 2\n",
    "        neu[\"inChannel\"] = channel\n",
    "        neu[\"channel\"] = channel\n",
    "        relu_K = 15\n",
    "        alpha = 25\n",
    "        neu[\"fs_phase\"] = 1\n",
    "        neu[\"fs_k\"] = relu_K\n",
    "        neu[\"fs_h\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        neu[\"fs_d\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        neu[\"fs_t\"] = (alpha * 2**(-relu_K) * np.array([float(2 ** (relu_K - i)) for i in range(1, relu_K + 1)]).astype(np.float32)).tolist()\n",
    "        file = \"./lenet5_fs_cfg/neuron{}.json\".format(src)\n",
    "        fp = open(file, 'w')\n",
    "        print(json.dumps(neu,indent=1), file=fp)\n",
    "        fp.close()\n",
    "        \n",
    "        \n",
    "    def to_fs_cfg(self, x, label):\n",
    "        k = 0\n",
    "        self.image_to_cfg(x[k], label[k], 0, 1)\n",
    "        \n",
    "        conv1_x = self.conv1(x)\n",
    "        pool1_x = self.pool1(conv1_x)\n",
    "        rule1_x = self.relu(pool1_x)\n",
    "        \n",
    "        self.conv_to_conv_cfg(rule1_x[0].size(), [2,2,2], [6,5,5,1], self.conv1.weight, 1, 2)\n",
    "        \n",
    "        conv2_x = self.conv2(rule1_x)\n",
    "        pool2_x = self.pool2(conv2_x)\n",
    "        rule2_x = self.relu(pool2_x)\n",
    "        \n",
    "        self.conv_to_conv_cfg(rule2_x[0].size(), [2,2,2], [16,5,5,1], self.conv2.weight, 2, 3, flat=True)\n",
    "        \n",
    "        flat_x = rule2_x.view(-1, self.num_flat_features(rule2_x))\n",
    "        fc1_x = self.relu(self.fc1(flat_x))\n",
    "        self.linear_to_linear_cfg(self.fc1.out_features, self.fc1.weight, 3, 4, pool=[2,2,2])\n",
    "        \n",
    "        fc2_x = self.relu(self.fc2(fc1_x))\n",
    "        self.linear_to_linear_cfg(self.fc2.out_features, self.fc2.weight, 4, 5)\n",
    "        \n",
    "        x = self.fc3(fc2_x)\n",
    "        self.linear_to_linear_cfg(self.fc3.out_features, self.fc3.weight, 5, 6)\n",
    "\n",
    "        self.output_cfg(self.fc3.out_features, 6)\n",
    "        \n",
    "        return x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = lenet55()\n",
    "from torch.autograd import Variable\n",
    "criterion = nn.CrossEntropyLoss() # use a Classification Cross-Entropy loss\n",
    "optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "29\n",
      "tensor(2)\n",
      "tensor([ 0.0000,  1.3145, 19.4565,  1.5160,  3.3768,  0.0000,  0.0000,  0.0000,\n",
      "         1.7677,  0.0000])\n",
      "tensor([0., 8., 9., 7., 5., 0., 0., 0., 5., 0.])\n"
     ]
    }
   ],
   "source": [
    "net.load_state_dict(torch.load('./model.pth'))\n",
    "\n",
    "for data in testloader:\n",
    "    images, labels = data\n",
    "    \n",
    "    outputs = net.fs_forward(Variable(images), -1)\n",
    "    spikes = torch.zeros_like(outputs)\n",
    "    for time in range(0, 90):\n",
    "        if (time==0):\n",
    "            outputs = net.fs_forward(Variable(images), time)\n",
    "        else:\n",
    "            outputs = outputs + net.fs_forward(Variable(images*0.0), time)\n",
    "            spikes = spikes + net.fs_relu5.spike\n",
    "    print(labels[0])\n",
    "    print(outputs[0])\n",
    "    print(spikes[0])\n",
    "    # image_to_cfg(images[0], labels[0], 0, 1)\n",
    "    net.to_fs_cfg(images, labels)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1,   200] loss: 0.017\n",
      "[1,   400] loss: 0.020\n",
      "[1,   600] loss: 0.021\n",
      "[2,   200] loss: 0.017\n",
      "[2,   400] loss: 0.019\n",
      "[2,   600] loss: 0.018\n",
      "[3,   200] loss: 0.017\n",
      "[3,   400] loss: 0.016\n",
      "[3,   600] loss: 0.021\n",
      "[4,   200] loss: 0.017\n",
      "[4,   400] loss: 0.018\n",
      "[4,   600] loss: 0.018\n",
      "[5,   200] loss: 0.017\n",
      "[5,   400] loss: 0.020\n",
      "[5,   600] loss: 0.016\n",
      "[6,   200] loss: 0.015\n",
      "[6,   400] loss: 0.021\n",
      "[6,   600] loss: 0.018\n",
      "[7,   200] loss: 0.016\n",
      "[7,   400] loss: 0.015\n",
      "[7,   600] loss: 0.019\n",
      "[8,   200] loss: 0.016\n",
      "[8,   400] loss: 0.016\n",
      "[8,   600] loss: 0.016\n",
      "[9,   200] loss: 0.014\n",
      "[9,   400] loss: 0.017\n",
      "[9,   600] loss: 0.018\n",
      "[10,   200] loss: 0.016\n",
      "[10,   400] loss: 0.016\n",
      "[10,   600] loss: 0.017\n",
      "[11,   200] loss: 0.014\n",
      "[11,   400] loss: 0.016\n",
      "[11,   600] loss: 0.017\n",
      "[12,   200] loss: 0.015\n",
      "[12,   400] loss: 0.016\n",
      "[12,   600] loss: 0.014\n",
      "[13,   200] loss: 0.015\n",
      "[13,   400] loss: 0.016\n",
      "[13,   600] loss: 0.015\n",
      "[14,   200] loss: 0.014\n",
      "[14,   400] loss: 0.014\n",
      "[14,   600] loss: 0.017\n",
      "[15,   200] loss: 0.013\n",
      "[15,   400] loss: 0.015\n",
      "[15,   600] loss: 0.017\n",
      "[16,   200] loss: 0.016\n",
      "[16,   400] loss: 0.013\n",
      "[16,   600] loss: 0.016\n",
      "[17,   200] loss: 0.013\n",
      "[17,   400] loss: 0.013\n",
      "[17,   600] loss: 0.015\n",
      "[18,   200] loss: 0.013\n",
      "[18,   400] loss: 0.013\n",
      "[18,   600] loss: 0.016\n",
      "[19,   200] loss: 0.013\n",
      "[19,   400] loss: 0.014\n",
      "[19,   600] loss: 0.013\n",
      "[20,   200] loss: 0.013\n",
      "[20,   400] loss: 0.011\n",
      "[20,   600] loss: 0.013\n",
      "Finished Training\n"
     ]
    }
   ],
   "source": [
    "net.load_state_dict(torch.load('./model.pth'))\n",
    "\n",
    "for epoch in range(20): # loop over the dataset multiple times\n",
    "    \n",
    "    running_loss = 0.0\n",
    "    for i, data in enumerate(trainloader, 0):\n",
    "        # get the inputs\n",
    "        inputs, labels = data\n",
    "        \n",
    "        # wrap them in Variable\n",
    "        inputs, labels = Variable(inputs), Variable(labels)\n",
    "        \n",
    "        # zero the parameter gradients\n",
    "        optimizer.zero_grad()\n",
    "        \n",
    "        # forward + backward + optimize\n",
    "        outputs = net(inputs)\n",
    "        loss = criterion(outputs, labels)\n",
    "        \n",
    "        loss.backward()        \n",
    "        optimizer.step()\n",
    "        \n",
    "        # print statistics\n",
    "        running_loss += loss.item()\n",
    "        if i % 200 == 199: # print every 200 mini-batches\n",
    "            print('[%d, %5d] loss: %.3f' % (epoch+1, i+1, running_loss / 200))\n",
    "            running_loss = 0.0\n",
    "torch.save(net.state_dict(), './model.pth')\n",
    "print('Finished Training')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(9846)\n",
      "Accuracy of the network on the 10000 test images: 98.459999 %\n"
     ]
    }
   ],
   "source": [
    "# net.load_state_dict(torch.load('./model.pth'))\n",
    "correct = 0\n",
    "total = 0\n",
    "for data in testloader:\n",
    "    images, labels = data\n",
    "    outputs = net(Variable(images))\n",
    "    \n",
    "    \n",
    "    _, predicted = torch.max(outputs.data, 1)\n",
    "    total += labels.size(0)\n",
    "    correct += (predicted == labels).sum()\n",
    "print(correct)\n",
    "print('Accuracy of the network on the 10000 test images: %f %%' % (100 * correct / total))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy of     0 : 100.000000 %\n",
      "Accuracy of     1 : 100.000000 %\n",
      "Accuracy of     2 : 100.000000 %\n",
      "Accuracy of     3 : 100.000000 %\n",
      "Accuracy of     4 : 100.000000 %\n",
      "Accuracy of     5 : 100.000000 %\n",
      "Accuracy of     6 : 100.000000 %\n",
      "Accuracy of     7 : 100.000000 %\n",
      "Accuracy of     8 : 100.000000 %\n",
      "Accuracy of     9 : 100.000000 %\n"
     ]
    }
   ],
   "source": [
    "class_correct = list(0. for i in range(10))\n",
    "class_total = list(0. for i in range(10))\n",
    "for data in testloader:\n",
    "    images, labels = data\n",
    "    outputs = net(Variable(images))\n",
    "    _, predicted = torch.max(outputs.data, 1)\n",
    "    c = (predicted == labels).squeeze()\n",
    "    for i in range(4):\n",
    "        label = labels[i]\n",
    "        class_correct[label] += c[i]\n",
    "        class_total[label] += 1\n",
    "\n",
    "for i in range(10):\n",
    "    print('Accuracy of %5s : %2f %%' % (classes[i], 100 * class_correct[i] / class_total[i]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "29\n",
      "tensor([ 4.2320,  0.0000, 14.1571,  0.5592,  0.0000,  0.0000,  0.0000,  9.8022,\n",
      "         3.8910,  0.0977])\n",
      "29\n",
      "tensor([ 3.8017,  0.0000, 24.9992,  5.4451,  0.0000,  0.0000,  0.0000,  0.0000,\n",
      "         6.2126,  0.0000])\n",
      "29\n",
      "tensor([ 0.0000,  8.2138, 24.9992,  8.3893,  0.0000,  0.0000,  0.0000, 14.1251,\n",
      "         6.3599,  0.0000])\n",
      "29\n",
      "tensor([ 4.5441,  0.0000,  0.0000,  3.3157,  3.4180,  2.1713, 20.4315,  0.0000,\n",
      "         0.0000,  0.0000])\n",
      "29\n",
      "tensor([24.9992,  0.0000,  0.5707,  0.0000,  0.0000,  0.0000,  1.4008,  3.1685,\n",
      "         1.2047,  4.8698])\n",
      "29\n",
      "tensor([ 0.0000,  0.0000,  0.0000,  0.0000, 14.8170,  0.0000,  0.0000,  0.2266,\n",
      "         0.0000,  0.0000])\n",
      "29\n",
      "tensor([ 0.0000,  0.0000,  0.4158,  4.3083,  0.0000,  0.0000,  0.0000, 13.8832,\n",
      "         0.0000,  3.4386])\n",
      "29\n",
      "tensor([ 0.6096, 14.2265,  0.0000,  0.0000,  0.0000,  0.0000,  1.9615,  1.8364,\n",
      "         2.0447,  0.0000])\n",
      "29\n",
      "tensor([ 0.0000, 12.5854,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  3.0914,\n",
      "         0.8537,  0.0000])\n",
      "29\n",
      "tensor([ 0.0000, 16.9090,  0.1442,  0.0000,  2.3392,  0.0000,  0.0000,  1.9119,\n",
      "         3.8498,  0.0000])\n",
      "Accuracy of the network on the 10000 test images: 98.440002 %\n"
     ]
    }
   ],
   "source": [
    "\n",
    "correct = 0\n",
    "total = 0\n",
    "for data in testloader:\n",
    "    images, labels = data\n",
    "    outputs = net.fs_forward(Variable(images), -1)\n",
    "    for time in range(0, 90):\n",
    "        if (time==0):\n",
    "            outputs = net.fs_forward(Variable(images), time)\n",
    "        else:\n",
    "            outputs = outputs + net.fs_forward(Variable(images*0.0), time)\n",
    "    print(outputs[0])\n",
    "    _, predicted = torch.max(outputs.data, 1)\n",
    "    total += labels.size(0)\n",
    "    correct += (predicted == labels).sum()\n",
    "\n",
    "print('Accuracy of the network on the 10000 test images: %f %%' % (100 * correct / total))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([6, 1, 5, 5])\n",
      "torch.Size([16, 6, 5, 5])\n",
      "torch.Size([120, 256])\n",
      "torch.Size([84, 120])\n",
      "torch.Size([10, 84])\n",
      "lenet55(\n",
      "  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), bias=False)\n",
      "  (pool1): AvgPool2d(kernel_size=2, stride=2, padding=0)\n",
      "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1), bias=False)\n",
      "  (pool2): AvgPool2d(kernel_size=2, stride=2, padding=0)\n",
      "  (relu): ReLU()\n",
      "  (fc1): Linear(in_features=256, out_features=120, bias=False)\n",
      "  (fc2): Linear(in_features=120, out_features=84, bias=False)\n",
      "  (fc3): Linear(in_features=84, out_features=10, bias=False)\n",
      "  (fs_pool1): fs_pool(\n",
      "    (pool): AvgPool2d(kernel_size=2, stride=2, padding=0)\n",
      "  )\n",
      "  (fs_pool2): fs_pool(\n",
      "    (pool): AvgPool2d(kernel_size=2, stride=2, padding=0)\n",
      "  )\n",
      "  (fs_relu1): fs_relu()\n",
      "  (fs_relu2): fs_relu()\n",
      "  (fs_relu3): fs_relu()\n",
      "  (fs_relu4): fs_relu()\n",
      "  (fs_relu5): fs_relu()\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "for weight in net.parameters():\n",
    "    print(weight.size())\n",
    "print(net)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 28, 28])\n",
      "tensor(2)\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# functions to show an image\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "%matplotlib inline\n",
    "def imshow(img):\n",
    "    img = img / 2 + 0.5 # unnormalize\n",
    "    npimg = img.numpy()\n",
    "    plt.imshow(np.transpose(npimg, (1,2,0)))\n",
    "    \n",
    "for data in testloader:\n",
    "    images, labels = data\n",
    "    k=2\n",
    "    print(images[k].size())\n",
    "    print(labels[k])\n",
    "    imshow(images[k])\n",
    "    break;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "def image_to_cfg(image, label, src, tgt):\n",
    "    cfg = {}\n",
    "    cfg[\"channel\"] = image.size()[0]\n",
    "    cfg[\"height\"] = image.size()[1]\n",
    "    cfg[\"width\"] = image.size()[2]\n",
    "    cfg[\"label\"] = int(label)\n",
    "    cfg[\"data\"] = image.numpy().tolist()\n",
    "    # featrues\n",
    "    file = \"./lenet5_fs_cfg/features_map{}.json\".format(src)\n",
    "    fp = open(file,'w')\n",
    "    print(json.dumps(cfg),file=fp)\n",
    "    fp.close()\n",
    "    # axon\n",
    "    syn = {}\n",
    "    axon = []\n",
    "    for c in range(cfg[\"channel\"]):\n",
    "        for h in range(cfg[\"height\"]):\n",
    "            for w in range(cfg[\"width\"]):\n",
    "                axon.append([tgt, c, h, w])\n",
    "    syn[\"axons\"] = axon\n",
    "    syn[\"denrites\"] = []\n",
    "    file = \"./lenet5_fs_cfg/synapse{}.json\".format(src)\n",
    "    fp = open(file, 'w')\n",
    "    print(json.dumps(syn), file=fp)\n",
    "\n",
    "def conv_to_cfg(features, pool, conv, weight, src, tgt):\n",
    "    axon = []\n",
    "    cfg[\"channel\"] = features[0]\n",
    "    cfg[\"height\"] = features[1]\n",
    "    cfg[\"width\"] = features[2]\n",
    "    for c in range(cfg[\"channel\"]):\n",
    "        for h in range(cfg[\"height\"]):\n",
    "            for w in range(cfg[\"width\"]):\n",
    "                axon.append([tgt,c,h,w])\n",
    "    cfg[\"axons\"] = axon\n",
    "    \n",
    "    cfg[\"pool_h\"] = pool[0]\n",
    "    cfg[\"pool_w\"] = pool[1]\n",
    "    cfg[\"pool_s\"] = pool[2]\n",
    "    \n",
    "    cfg[\"conv_c\"] = conv[0]\n",
    "    cfg[\"conv_h\"] = conv[1]\n",
    "    cfg[\"conv_w\"] = conv[2]\n",
    "    cfg[\"conv_s\"] = conv[3]\n",
    "    \n",
    "    cfg[\"denrites\"] = weight.numpy().tolist()\n",
    "    file = \"./lenet5_fs_cfg/synapse{}.json\".format(src)\n",
    "    fp = open(file, 'w')\n",
    "    print(json.dumps(syn), file=fp)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'cfg' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-20-a5b99a48a9ad>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      4\u001b[0m     \u001b[0mimage_to_cfg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mimages\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlabels\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m     \u001b[0mnet\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mto_fs_cfg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mimages\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlabels\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-7-07ec6d783abb>\u001b[0m in \u001b[0;36mto_fs_cfg\u001b[1;34m(self, x, label)\u001b[0m\n\u001b[0;32m    139\u001b[0m         \u001b[0mrule1_x\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrelu\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpool1_x\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    140\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 141\u001b[1;33m         \u001b[0mconv_to_cfg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrule1_x\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;36m6\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconv1\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mweight\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    142\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    143\u001b[0m         \u001b[0mconv2_x\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconv2\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrule1_x\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-17-28f4341397aa>\u001b[0m in \u001b[0;36mconv_to_cfg\u001b[1;34m(features, pool, conv, weight, src, tgt)\u001b[0m\n\u001b[0;32m     27\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mconv_to_cfg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfeatures\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpool\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mconv\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mweight\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msrc\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtgt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     28\u001b[0m     \u001b[0maxon\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 29\u001b[1;33m     \u001b[0mcfg\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"channel\"\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfeatures\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     30\u001b[0m     \u001b[0mcfg\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"height\"\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfeatures\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     31\u001b[0m     \u001b[0mcfg\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"width\"\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfeatures\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mNameError\u001b[0m: name 'cfg' is not defined"
     ]
    }
   ],
   "source": [
    "for data in testloader:\n",
    "    images, labels = data\n",
    "    \n",
    "    image_to_cfg(images[0], labels[0], 0, 1)\n",
    "    \n",
    "    net.to_fs_cfg(images, labels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
